#include "internal/barray.h"

#include <string.h>

void *bit_array_create(size_t size) {
    size_t bsize = ((size + 7) >> 3) + sizeof(size_t);
    void *ret = calloc(1, bsize);
    if (ret)
        *(size_t *)ret = size;
    return ret;
}

void bit_array_destroy(void *array) {
    if (array)
        free(array);
}

void *bit_array_dup(void *array) {
    if (array) {
        size_t *size = (size_t *)array;
        size_t bsize = ((*size + 7) >> 3) + sizeof(*size);
        void *ret = malloc(bsize);
        if (ret)
            memcpy(ret, array, bsize);
        return ret;
    }
    return NULL;
}

void bit_array_reset(void *array) {
    if (array) {
        size_t *size = (size_t *)array;
        size_t bsize = (*size + 7) >> 3;
        memset(size + 1, 0, bsize);
    }
}

void bit_array_set(void *array, size_t bit) {
    if (array) {
        size_t *size = (size_t *)array;
        if (bit < *size) {
            unsigned char *ptr = (unsigned char *)(size + 1);
            ptr[bit >> 3] |= (1U << (bit & 7));
        }
    }
}

void bit_array_set_range(void *array, size_t bit, size_t count) {
    if (array && count) {
        size_t *size = (size_t *)array;
        if (bit < *size) {
            unsigned char *ptr = (unsigned char *)(size + 1);
            size_t i;
            for (i = bit; i < *size && i < bit + count; ++i)
                ptr[i >> 3] |= (1U << (i & 7));
        }
    }
}

int bit_array_test(void *array, size_t bit) {
    if (array) {
        size_t *size = (size_t *)array;
        if (bit < *size) {
            unsigned char *ptr = (unsigned char *)(size + 1);
            if (ptr[bit >> 3] & (1U << (bit & 7))) {
                return 1;
            }
        }
    }
    return 0;
}

int bit_array_test_range(void *array, size_t bit, size_t count) {
    if (array) {
        size_t *size = (size_t *)array;
        if (bit < *size) {
            unsigned char *ptr = (unsigned char *)(size + 1);
            if ((bit & 7) && (count > 8)) {
                while ((bit < *size) && count && (bit & 7)) {
                    if (ptr[bit >> 3] & (1U << (bit & 7)))
                        return 1;
                    bit++;
                    count--;
                }
            }
            if (!(bit & 7)) {
                while (((*size - bit) >= 8) && (count >= 8)) {
                    if (ptr[bit >> 3])
                        return 1;
                    bit += 8;
                    count -= 8;
                }
            }
            while ((bit < *size) && count) {
                if (ptr[bit >> 3] & (1U << (bit & 7)))
                    return 1;
                bit++;
                count--;
            }
        }
    }
    return 0;
}

void bit_array_clear(void *array, size_t bit) {
    if (array) {
        size_t *size = (size_t *)array;
        if (bit < *size) {
            unsigned char *ptr = (unsigned char *)(size + 1);
            ptr[bit >> 3] &= ~(1U << (bit & 7));
        }
    }
}

void bit_array_clear_range(void *array, size_t bit, size_t count) {
    if (array && count) {
        size_t *size = (size_t *)array;
        if (bit < *size) {
            unsigned char *ptr = (unsigned char *)(size + 1);
            size_t i;
            for (i = bit; i < *size && i < bit + count; ++i)
                ptr[i >> 3] &= ~(1U << (i & 7));
        }
    }
}

void bit_array_merge(void *dest, void *source, size_t offset) {
    if (dest && source) {
        size_t *dsize = (size_t *)dest;
        size_t *ssize = (size_t *)source;
        size_t soffset = 0;
        while (offset < *dsize && soffset < *ssize) {
            if (bit_array_test(source, soffset)) {
                bit_array_set(dest, offset);
            }
            soffset++;
            offset++;
        }
    }
}

void bit_array_mask(void *dest, void *source, size_t offset) {
    if (dest && source) {
        size_t *dsize = (size_t *)dest;
        size_t *ssize = (size_t *)source;
        size_t soffset = 0;
        while (offset < *dsize && soffset < *ssize) {
            if (bit_array_test(source, soffset)) {
                bit_array_clear(dest, offset);
            }
            soffset++;
            offset++;
        }
    }
}
